import Konva from "konva";
import { Draw } from "../../../Draw/Draw";
import { setStageCursor } from "./stage-cursor";
import { IKonva } from "../../../Interface/IKonva";
import { getPointsPosition } from "../../../Utils";

/**
 * 监听 stage 鼠标移动事件：
 *   1. 处于插入状态，判断是否处于插入过程中
 *   2. 处于选择状态，判断是否处于选择过程中
 *   3. 插入的元素类型 有 insertType 属性，执行 konvaGraph 的构建元素，通过返回值 动态设置宽高 位置关系，实现插入
 *   4. 框选元素，通过监听 stage 鼠标移动事件，实时计算位置，实现框选
 */
export function stageMouseMove(e: IKonva.Event, draw: Draw) {
  e.cancelBubble = true;

  const stage = draw.getStage();
  const { insertShape, inserting, selecting } = stage.attrs;
  const { translateMode, translating } = stage.attrs;

  // 判断是否处于平移状态下
  if (translateMode && translating) return translateHandle(e, draw);

  // 处于插入状态，判断是否处于插入过程中
  if (insertShape && inserting) return insertHandle(e, draw);

  // 处于选择状态，判断是否处于选择过程中
  if (selecting) return selectHandle(e, draw);
}

// 插入元素事件
function insertHandle(e: IKonva.Event, draw: Draw) {
  const stage = draw.getStage();
  const layer = draw.getLayer()!;
  const { insertShapeID, ssx, ssy } = stage.attrs;
  const group = <Konva.Group>layer.findOne(`#${insertShapeID}`);
  if (!group) return;

  const { offsetX, offsetY } = e.evt;
  const dx = offsetX - ssx;
  const dy = offsetY - ssy;

  // 线段特殊处理
  if (group.className === "Arrow") {
    // 不同线段类型有不同的处理方案哈，需要根据 insertType 识别
    const line = <Konva.Arrow>layer.findOne(`#${insertShapeID}`);
    const { insertShape } = group.attrs;
    if (insertShape === "line-z" || insertShape === "line-w") {
      // 当前 折线的起点 ： ssx ssy
      // 当前 鼠标点 ssx+dx ssy+dy

      // 求交点
      const { leftTop, rightTop, leftBottom, rightBottom } = getPointsPosition(
        ssx,
        ssy,
        ssx + dx,
        ssy + dy
      );
      // 剔除自身
      const position = analysisPoinsArray(
        [leftTop, rightTop, leftBottom, rightBottom],
        [ssx, ssy],
        [ssx + dx, ssy + dy]
      );
      if (position.length) {
        if (dy < 0)
          line.points([
            0,
            0,
            position[1][0] - ssx,
            position[1][1] - ssy,
            dx,
            dy,
          ]);
        else
          line.points([
            0,
            0,
            position[0][0] - ssx,
            position[0][1] - ssy,
            dx,
            dy,
          ]);
      } else line.points([0, 0, dx, dy]);
    } else if (insertShape === "line-zz" || insertShape === "line-ww") {
      // 取 centerTop、centerBottom 两个点
      const { centerTop, centerBottom } = getPointsPosition(
        ssx,
        ssy,
        ssx + dx,
        ssy + dy
      );
      if (dy < 0) {
        line.points([
          0,
          0,
          centerBottom[0] - ssx,
          centerBottom[1] - ssy,
          centerTop[0] - ssx,
          centerTop[1] - ssy,
          dx,
          dy,
        ]);
      }
      // 还需要纠正谁在前
      else
        line.points([
          0,
          0,
          centerTop[0] - ssx,
          centerTop[1] - ssy,
          centerBottom[0] - ssx,
          centerBottom[1] - ssy,
          dx,
          dy,
        ]);
    } else {
      // dx dy 就是基于直线的 x y 的偏移量，因此可以直接赋值
      line.points([0, 0, dx, dy]);
    }
    return;
  }

  /**
   * 这个过程不需要 draw.render 不然会导致卡顿
   *  mouseup 才生成真实的图形，并渲染缩略图
   *  这个过程就计算实际的宽高是为了自定义图形中使用，不然都是1 1无法绘制图形
   *  同时，因为初始宽高都为 1 ，因此此处可以直接使用偏移量作为 宽高
   */

  const width = Math.abs(dx);
  const height = Math.abs(dy);

  group?.size({ width, height });
  group?.scale({ x: 1, y: 1 });

  // 反方向还需要修正 x y
  if (dx < 0) group.x(ssx - width);
  if (dy < 0) group.y(ssy - height);

  // 找到插入的元素内部的实际元素 - 更正宽高问题
  group.children.forEach((node) => {
    if (node.className === "Circle") {
      // 圆的半径始终等于 当前位置与初始位置的距离之和
      const radius = Math.sqrt(width ** 2 + height ** 2);
      node?.setAttrs({ radius, x: radius / 2, y: radius / 2 });
    }
    if (node.className === "Ellipse") {
      node?.setAttrs({
        radiusX: width,
        radiusY: height,
        x: width / 2,
        y: height / 2,
      });
    } else node?.size({ width, height });
  });
}

// 框选事件
function selectHandle(e: IKonva.Event, draw: Draw) {
  const stage = draw.getStage();
  const root = draw.getRootBox();

  setStageCursor(draw, "grab");

  // 框选框
  const selectBoxCss = ".konva-root-container-frame_selected";
  const selectBox = <HTMLDivElement>root.querySelector(selectBoxCss);

  // 解析位置、计算偏移量
  const { ssx, ssy } = stage.attrs;
  const { offsetX, offsetY } = e.evt;
  const dx = offsetX - ssx;
  const dy = offsetY - ssy;

  // 优化虚拟框选，某些元素移动会导致页面出现框选虚影（width < 2px; height < 2px）
  if (Math.abs(dx) < 2 || Math.abs(dy) < 2) return;

  let left = selectBox.style.left;
  let top = selectBox.style.top;

  const width = Math.abs(dx) + "px";
  const height = Math.abs(dy) + "px";

  // 位置纠正
  if (dx < 0) left = offsetX + "px";
  if (dy < 0) top = offsetY + "px";

  selectBox.style.left = left;
  selectBox.style.top = top;
  selectBox.style.width = width;
  selectBox.style.height = height;
  selectBox.style.opacity = "0.6";
}

// 平移
function translateHandle(e: IKonva.Event, draw: Draw) {
  setStageCursor(draw, "grabbing");

  // 这里计算出移动距离 - mousedown 的时候，记录初始位置(tpx 、tpy)
  const { tpx, tpy, baseStyle } = draw.getStage().attrs;
  const { pageX, pageY } = e.evt;
  const dx = pageX - tpx;
  const dy = pageY - tpy;

  // 然后调用 eventBus 触发事件，将参数传递给 VeritureScroll 进行 translate 平移
  const eventBus = draw.getEventBus();
  eventBus.emit("translate", { dx, dy, baseStyle });
}

/**
 * 工具函数 - 解析 point1 point2 交点的合法性剔除自身
 */
function analysisPoinsArray(arr: number[][], p1: number[], p2: number[]) {
  arr.forEach((item) => {
    const [x, y] = item;
    if ((x === p1[0] && y === p1[1]) || (x === p2[0] && y === p2[1]))
      item[0] = NaN;
  });

  return arr.filter((i) => !isNaN(i[0]));
}
